스펙은 MySQL 이 아니라 MySQL 모드의 H2 에서 돈다. 스펙이 도는 스키마는 실제 데이터베이스의 커밋된 덤프 schema/schema.sql 에서 만든다.
test/com/aha00a/tests 아래 두 파일이 모든 스펙에 필요한 것을 들고 있다.
TestApplication - 인메모리 SyncCacheApi 대역(진짜 캐시 모듈은 Redis 인데 테스트는 그것을 띄우지 않는다), H2 URL, 충돌 없는 데이터베이스 이름, 그리고 모든 스펙이 출발하는 Guice 구성.TestSchema - createAll() 이 덤프에서 모든 테이블을 만든다. 골라 만든 부분집합이 아니다: 외래 키가 테이블들을 묶고 있고, 스펙이 자기가 필요하다고 믿는 것을 나열하는 것이 그 목록이 낡는 방식이다. 인메모리 DB 에서 안 쓰는 테이블은 비용이 없다.다른 것이 필요한 스펙은 공용 구성을 다시 적는 대신 거기에 얹는 것으로 말한다. ApiV1FilterSpec 은 진짜 필터 체인을 돌리므로 play.http.filters 만 덮고 나머지는 그대로 둔다.
Guice 애플리케이션이 쓰는 데이터베이스 URL 과 직접 여는 DriverManager 연결의 URL 은 같은 TestApplication.h2Url 에서 와야 한다. 같은 이름의 철자 둘은 서로 다른 인메모리 데이터베이스 둘이고, 스펙은 불일치가 아니라 없는 테이블로 실패한다.
스키마를 바꾸면 schemaDump.sh 로 덤프를 새로 뜬다. 스펙은 자동으로 따라온다. 스펙이 의존하는 컬럼이 사라지면 조용히 낡는 사본 대신 실패하는 스펙으로 나타난다.
스크립트는 mysqldump 가 쓰는 헤더를 지우는데, 지우는 이유가 둘이고 하나로 오해하기 쉽다. 호스트와 데이터베이스 이름은 공개 저장소에 있으면 안 되는 인프라 이름이라 로컬 설정을 가리키는 자리로 바꾼다. mysqldump 클라이언트 버전(Distrib …, for …)·완료 시각·AUTO_INCREMENT 카운터는 실행마다 바뀌는 잡음일 뿐인데, 두면 안 바뀐 스키마가 diff 로 도착한다. 서버 버전 줄(-- Server version)은 지우지 않으므로, 엔진을 올리면 그 한 줄이 diff 로 나온다 — 스키마가 바뀐 것이 아니다. "진짜" 헤더를 되찾겠다고 sed 하나를 지우면, 그 줄이 눌러 두고 있던 둘 중 하나가 되돌아온다.
읽어 들일 때 손보는 것이 셋 있다. 각각 스키마에 대한 의견 차가 아니라 MySQL 과 H2 의 차이다:
손본 것 | 왜 |
|---|---|
외래 키를 | 덤프는 알파벳 순이라 키가 아직 없는 테이블을 가리키기 일쑤다 — 게다가 |
| MySQL 에는 boolean 이 없어 |
유니크하지 않은 컬럼으로의 외래 키 제거 | MySQL 은 아무 인덱스 접두로의 키를 받아 준다. H2 는 대상이 유니크해야 하고 그러려고 유니크 인덱스를 만든다 — |
셋째 것은 실제 fidelity 손실이다: Page (site, name) 으로의 키들은 테스트에서 강제되지 않는다. TestSchema 는 그것을 이름이 아니라 대상으로 정한다 — 대상이 유니크하지 않은 키는 전부 빠진다 — 그래서 개수는 덤프가 그때 담고 있는 만큼이지 여기 적어 둘 수는 없다. 2026-08-17 에는 셋이었다:
grep -c 'REFERENCES `Page` (`site`, `name`)' schema/schema.sql
그 밖의 모든 것 — 모든 NOT NULL, default, ENUM 값 목록 — 은 운영이 가진 그대로다. 외래 키의 ON DELETE 동작(CASCADE·SET NULL)도 2026-09-15 부터 그렇다 — 전에는 키를 ALTER 로 옮기며 그 절을 떨어뜨려서, 운영에서 cascade 되는 삭제가 테스트에서는 부모를 지우려다 걸렸다.
마지막 문장은 보기보다 무겁다. 덤프는 운영이 아니라 개발 스키마에서 뜨기 때문이다. 아래 --check 는 커밋된 파일을 그것이 나온 스키마와 비교한다; 그 스키마를 이 주장의 대상인 운영과 비교하는 것은 없다. 한쪽에만 적용된 evolution 은 스펙이 운영에 없는 모양을 테스트하게 만들고, --check 는 여전히 파일이 최신이라고 말한다.
두 스키마는 결국 같아지지만, 언제인지는 모른다. 2026-09-03 실측 — wiki_aha00a_com 과 wiki_aha00a_com_dev 는 같은 서버의 다른 스키마이고, Page 22,748 대 22,747, Site·User 는 같다. 개발 쪽 play_evolutions 의 68·69 행은 적용 시각까지 운영과 같다 — 개발이 운영에서 갱신되며 행을 그대로 물려받았다는 뜻이다. 그래서 evolution 을 배포한 9-02 에는 --check 가 그 테이블만큼 차이를 보고했고 하루 뒤에는 통과했다. 갱신이 언제 어떻게 도는지는 이 저장소에 적혀 있지 않으니, 차이가 사라졌다고 해서 확인이 끝난 것은 아니다 — 위 문단의 함정은 그대로다.
확인은 덤프 한 번과 diff 하나다. 헤더가 맞아떨어지도록 같은 정리를 거치고, 운영 스키마는 운영 저장소가 기록한 호스트와 관리자 자격증명으로 떠서, 커밋된 파일과 비교한다:
ssh <host> "mysqldump --defaults-file=<admin cnf> --default-character-set=utf8mb4 \ --no-data --no-tablespaces --column-statistics=0 <production schema>" \ sed 's/ AUTO_INCREMENT=[0-9]*//g' \ sed 's/^-- Dump completed on .*//g' \ sed 's/Distrib [0-9]*.[0-9]*.[0-9]*, for .*/Distrib #.#.#, for OS/g' \ sed 's/^-- Host: .*/-- Host: (from local config) Database: (from local config)/' \ diff schema/schema.sql -
2026-08-17 실측: 동일. 양쪽 다 24개 테이블 488줄. 두 스키마는 갈라지지 않았고, 갈라지면 이것이 알아내는 방법이다.
이것이 대체한 손으로 쓴 사본은 운영과 여러 군데가 달랐다 — 정수 폭 아홉, VARCHAR(255) 로 선언된 TEXT 컬럼 둘, 진짜 컬럼이라면 거부할 targetType 을 스펙이 넣을 수 있도록 VARCHAR 로 선언된 ENUM 컬럼 셋, evolution 55 가 떨어뜨린 UserSite 테이블 하나.
덤프로 바꾸자 이번에는 픽스처에서 더 나왔다: 스펙들이 abbr 없는 Site 행(운영에서는 NOT NULL, default 없음), 뒤에 Page 가 없는 PageMeta 행, 필수 컬럼 여덟 개가 빠진 AccessLog 행을 넣고 있었다. 전부, 그것을 받아 줄 만큼 느슨한 스키마를 상대로 통과해 온 것이다.
UserMergeSpec 은 운영의 Page 에 User 로의 외래 키가 없다는 믿음으로, 그 키를 단 자기만의 최소 Page 테이블까지 만들어 갖고 있었다. 있다 — Page_User_seq_fk — 그래서 대역은 불필요했고 스펙은 이제 진짜 테이블을 쓴다.
MODIFY … COLLATE 에서 걸린다. H2 가 MySQL 모드에서도 거부하는 MySQL 문법이다.문법 | 예 |
|---|---|
컬럼 위치 지정 |
|
|
|
한 문장에서 키를 지우고 더하기 |
|
|
|
MySQL 날짜 함수 |
|
ALTER 만이 아니다 — TRUNCATE, rename table, 데이터를 고치는 UPDATE 도 있다. 덤프에는 하나도 없다 — CREATE TABLE 뿐이다 — evolution 이 안 되는 곳에서 덤프가 되는 이유가 그것이다.
schema/schema.sql 갱신은 여전히 손으로 하는 단계이고, 아무도 덤프하지 않은 스키마 변경은 스펙을 어제의 모양으로 테스트하게 놔둔다. 실패 양상은 전보다 순하다 — 스펙마다 제각각의 발명품 대신 스위트 전체가 하나의 낡은 스키마에서 돈다 — 하지만 그것을 일어나게 만드는 것은 없다.지금 있는 것은 물어보는 방법이고, 초록 스위트를 믿기 전에 돌려 볼 만큼 싸다:
sh ./schemaDump.sh --check 라이브 스키마를 같은 정리를 거쳐 덤프해 커밋된 파일과 diff 하고, 다르면 그 차이와 함께 0 아닌 코드로 끝난다. schema/schema.sql 은 건드리지 않는다(임시 파일 schema/.schema.sql.tmp 를 썼다가 지운다). 두 경로가 한 함수에서 나오는 것은 일부러다: 그 파이프라인의 사본 둘은 sed 하나씩 갈라져, 검사가 있지도 않은 차이를 보고하기 시작한다.
빌드에 물려 놓지는 않았다. 로컬 설정과 거기 적힌 데이터베이스가 필요해서, 자격증명 없는 기계의 테스트 실행이 엉뚱한 이유로 실패하게 된다.
Similar pages by cosine similarity. Words after page name are term frequency.